﻿using System;
using System.Linq;
using System.Collections.Generic;

namespace VirtualRink.Library
{
	public class LinqRepository : IRepository
	{
		VirtualRinkModelDataContext context;

		public LinqRepository()
			: this(new VirtualRinkModelDataContext())
		{
		}

		public LinqRepository(string connectionString)
			: this(new VirtualRinkModelDataContext(connectionString))
		{
		}

		public LinqRepository(VirtualRinkModelDataContext context)
		{
			this.context = context;
		}

		public VirtualRinkModelDataContext Context
		{
			get { return this.context; }
		}

		public League CreateLeague(string name)
		{
			League league = new League() { Name = name };
			league.CurrentDate = DateTime.Now;
			this.context.Leagues.InsertOnSubmit(league);
			this.context.SubmitChanges();
			return league;
		}

		public Conference CreateConference(string name, League league)
		{
			Conference conference = new Conference()
			{
				Name = name,
				League = league
			};
			this.context.Conferences.InsertOnSubmit(conference);
			this.context.SubmitChanges();
			return conference;
		}

		public Division CreateDivision(string name, Conference conference)
		{
			Division division = new Division() { Name = name, Conference = conference };
			this.context.Divisions.InsertOnSubmit(division);
			this.context.SubmitChanges();
			return division;
		}

		public Team CreateTeam(string name, string location, string owner, string coach, Division division)
		{
			Team team = new Team()
			{
				Name = name,
				Location = location,
				Owner = owner,
				Coach = coach,
				Division = division
			};
			this.context.Teams.InsertOnSubmit(team);
			this.context.SubmitChanges();
			return team;
		}

		public Player CreatePlayer(League league, string firstName, string lastName, int number, string shoots, string position)
		{
			Player player = new Player()
			{
				League = league,
				FirstName = firstName,
				LastName = lastName,
				Number = number,
				Position = position,
				Shoots = shoots
			};
			this.context.Players.InsertOnSubmit(player);
			this.context.SubmitChanges();
			return player;
		}


		public Contract DraftPlayer(Team team, Player player)
		{
			if (player.Contracts.Count > 0)
				throw new InvalidOperationException("Player cannot be drafted when they already have a contract.");

			if (player.League != team.Division.Conference.League)
				throw new InvalidOperationException("Team cannot draft a player from a different league.");

			//check to see if roster would be valid
			List<Player> players = new List<Player>();
			players.AddRange(team.Players);
			if (!Roster.CanAddPlayer(players, player))
				throw new InvalidOperationException("Cannot draft player onto a team without room.");

			Contract contract = new Contract() { Team = team, Player = player };
			this.context.Contracts.InsertOnSubmit(contract);

			player.Team = team;
			player.Contracts.Add(contract);

			this.context.SubmitChanges();

			return contract;
		}

		public void TradePlayers(Team team1, Player player1, Team team2, Player player2)
		{
			if (player1.Team == null)
				throw new InvalidOperationException("Player 1 does not belong to a team for a trade.");
			if (player2.Team == null)
				throw new InvalidOperationException("Player 2 does not belong to a team for a trade.");
			if (player1.Team != team1)
				throw new InvalidOperationException("Player 1 does not belong to team 1.");
			if (player2.Team != team2)
				throw new InvalidOperationException("Player 2 does not belong to team 2.");
			if (team1.Division.Conference.League.Id != team2.Division.Conference.League.Id)
				throw new InvalidOperationException("Players cannot be traded across different leagues.");

			if (!Roster.CanReplacePlayer(team1.Players, player1, player2)
				|| !Roster.CanReplacePlayer(team2.Players, player2, player1))
				throw new InvalidOperationException("Cannot trade players as it would result in invalid rosters on one of the teams.");

			player1.Team = team2;
			player2.Team = team1;

			Contract contract1 = new Contract() { Team = team1, Player = player2 };
			Contract contract2 = new Contract() { Team = team2, Player = player1 };

			this.context.Contracts.InsertOnSubmit(contract1);
			this.context.Contracts.InsertOnSubmit(contract2);

			this.context.SubmitChanges();

		}

		public Contract GetLatestContract(Player player)
		{
			return player.Contracts.OrderByDescending(c => c.ContractDate).FirstOrDefault();
		}

		public Contract ReleasePlayer(Team team, Player player)
		{
			if (player.Team != team)
				throw new InvalidOperationException("Player does not belong to that team.");

			Contract contract = new Contract() { Player = player };
			this.context.Contracts.InsertOnSubmit(contract);
			player.Team = null;
			player.TeamId = null;
			this.context.SubmitChanges();
			return contract;
		}

		public IEnumerable<Player> GetAvailablePlayers(League league)
		{
			return from p in this.context.Players where p.Team != null & p.LeagueId == league.Id select p;
		}

		public IEnumerable<Player> GetAvailablePlayers(League league, string position)
		{
			var where = PredicateBuilder.True<Player>();
			where = where.And(x => x.League.Id == league.Id);
			where = where.And(x => x.Position == position);
			where = where.And(x => x.Team == null);

			var query = from p in this.context.Players select p;
			query = query.Where(where);
			return query;
		}

		public Season CreateSeason(League league, DateTime startDate)
		{
			Season season = new Season() { League = league, StartDate = startDate };
			this.context.Seasons.InsertOnSubmit(season);
			this.context.SubmitChanges();
			return season;
		}

		public IEnumerable<Game> GetPlayedGames(Season season)
		{
			return from g in this.context.Games
				   where
					   g.SeasonId == season.Id & g.Played == true
				   select g;
		}

		public void UpdateStandings(Season season, List<Standing> newStandings)
		{
			var oldItems = from s in season.Standings select s;
			foreach (Standing oldItem in oldItems)
				this.context.Standings.DeleteOnSubmit(oldItem);
			foreach (Standing newItem in newStandings)
				this.context.Standings.InsertOnSubmit(newItem);
			this.context.SubmitChanges();
		}

		public IList<Team> GetTeamsByLeague(int leagueId)
		{
			//            var Cust = from c in db.Customers
			//join o in db.Orders on c.CustomerID equals o.CustomerID
			//where o.ShipCountry == “Germany”
			//select new { c.CompanyName, o.OrderDate, o.ShipCountry };

			var items = from t in this.context.Teams
						join d in this.context.Divisions on t.DivisionId equals d.Id
						join c in this.context.Conferences on d.ConferenceId equals c.Id
						where c.LeagueId == leagueId
						select t;
			return items.ToList();
		}

		public IList<Game> GetGamesWithoutPerformanceSummaries(int seasonId)
		{

			//outer join
			//var query=  from c in db.Customers
			//    join o in db.Orders
			//       on c.CustomerID equals o.CustomerID into sr
			//    from x in sr.DefaultIfEmpty()
			//    select new {
			//       CustomerID= c.CustomerID, ContactName=c.ContactName,
			//       OrderID = x.OrderID == null ? -1 : x.OrderID};


			var games = from p in this.context.TeamGamePerformances
						join g in this.context.Games on p.GameId equals g.Id
						into gp
						from x in gp.DefaultIfEmpty()
						where x.SeasonId == seasonId
						select x;
			return games.ToList();

			//var games = from g in this.context.Games
			//            join p in this.context.PlayerGamePerformances on g.Id equals p.GameId
			//            where g.SeasonId == seasonId
			//            select g;
			//return games;


		}

		public PlayerSeasonPerformance GetPlayerSeasonPerformance(int playerId, int seasonId)
		{
			var items = from p in this.context.PlayerSeasonPerformances
						where p.PlayerId == playerId & p.SeasonId == seasonId
						select p;
			if (items.Count() == 1)
				return items.First();
			else
				return null;

		}

		public TeamSeasonPerformance GetTeamSeasonPerformance(int teamId, int seasonId)
		{
			var items = from p in this.context.TeamSeasonPerformances
						where p.TeamId == teamId & p.SeasonId == seasonId
						select p;
			if (items.Count() == 1)
				return items.First();
			else
				return null;

		}

		public IEnumerable<Game> GetGamesByDate(Season season, DateTime dateTime)
		{
			var games = from g in season.Games
						where g.SeasonId == season.Id
							& g.GameDate == dateTime
						select g;
			return games;
		}

		public IEnumerable<Standing> GetStandings(int seasonId)
		{
			return from s in this.context.Standings
				   where s.SeasonId == seasonId
				   orderby s.Points, s.Wins
				   select s;
		}

		public IEnumerable<Game> GetTeamSchedule(int seasonId, int teamId)
		{
			return from g in this.context.Games
				   where g.SeasonId == seasonId
				   & (g.HomeTeamId == teamId | g.VisitingTeamId == teamId)
				   orderby g.GameDate
				   select g;
		}

		public void SubmitChanges()
		{
			this.context.SubmitChanges();
		}
	}
}
